/**
 * @brief httpServer的实现
 * @details
 * 1.成员变量包含了各种请求方法的路由表,标识了请求的资源路径和回调函数的映射,并且使用了regex进行正则匹配
 * 2.实现了tcpServer连接建立和消息到来的回调函数
 *  2.1 OnConnected函数设置请求的上下文
 *  2.2 OnMessage 获取请求上下文,进行解析得到request对象,然后进行路由和业务处理,发生数据之后重置上下文,短连接则直接进行关闭
 */
#pragma once

#include "tcpServer.hpp"
#include "httpRequest.hpp"
#include "httpResponse.hpp"
#include "httpContext.hpp"
#include "config.hpp"

namespace hdp
{
    class HttpServer
    {
    private:
        using Handler = std::function<void(const HttpRequest &, HttpResponse *, const std::string &)>;
        // std::regex没有默认的哈希函数,也没有默认的相等比较运算符,所以不能使用unordered_map来存储路由信息
        using Handlers = std::vector<std::pair<std::regex, Handler>>;
        Handlers _get_route;
        Handlers _post_route;
        Handlers _put_route;
        Handlers _delete_route;
        std::string _basedir; // 静态资源根目录
        TcpServer _server;

    private:
        void ErrorHandler(const HttpRequest &req, HttpResponse *rsp)
        {
            // 1. 组织一个错误展示页面
            std::string body;
            body += "<html>";
            body += "<head>";
            body += "<meta http-equiv='Content-Type' content='text/html;charset=utf-8'>";
            body += "</head>";
            body += "<body>";
            body += "<h1>";
            body += std::to_string(rsp->_statu);
            body += " ";
            body += Util::StatuDesc(rsp->_statu);
            body += "</h1>";
            body += "</body>";
            body += "</html>";
            // 2. 将页面数据，当作响应正文，放入rsp中
            rsp->SetContent(body, "text/html");
        }

        // 将HttpResponse中的要素按照http协议格式进行组织发送
        void WriteResponse(const PtrConnection &conn, const HttpRequest &req, HttpResponse &rsp)
        {
            // 1. 先完善头部字段
            if (req.Close() == true)
                rsp.SetHeader("Connection", "close");
            else
                rsp.SetHeader("Connection", "keep-alive");

            if (rsp._body.empty() == false && rsp.HasHeader("Content-Length") == false)
            {
                rsp.SetHeader("Content-Length", std::to_string(rsp._body.size()));
            }
            if (rsp._body.empty() == false && rsp.HasHeader("Content-Type") == false)
            {
                rsp.SetHeader("Content-Type", "application/octet-stream");
            }
            if (rsp._redirect_flag == true)
            {
                rsp.SetHeader("Location", rsp._redirect_url);
            }

            // 2. 将rsp中的要素，按照http协议格式进行组织
            std::stringstream rsp_str;
            rsp_str << req._version << " " << std::to_string(rsp._statu) << " "
                    << Util::StatuDesc(rsp._statu) << "\r\n";

            // for (auto &head : rsp._headers)
            // {
            //     rsp_str << head.first << ": " << head.second << "\r\n";
            // }
            for (auto &[key, val] : rsp._headers)
            {
                rsp_str << key << ": " << val << "\r\n";
            }
            rsp_str << "\r\n";
            rsp_str << rsp._body;

            // 3. 发送数据
            conn->Send(rsp_str.str().c_str(), rsp_str.str().size());
        }

        // 功能性请求的分类处理
        void Dispatcher(HttpRequest &req, HttpResponse *rsp, Handlers &handlers, const std::string &ip)
        {
            // 在对应请求方法的路由表中，查找是否含有对应资源请求的处理函数，有则调用，没有则发挥404
            // 思想：路由表存储的时键值对 -- 正则表达式 & 处理函数
            // 使用正则表达式，对请求的资源路径进行正则匹配，匹配成功就使用对应函数进行处理
            //   /numbers/(\d+)       /numbers/12345
            for (auto &handler : handlers)
            {
                const std::regex &re = handler.first;
                const Handler &functor = handler.second;
                bool ret = std::regex_match(req._path, req._matches, re);
                if (ret == false)
                {
                    continue;
                }
                return functor(req, rsp, ip); // 传入请求信息，和空的rsp，执行处理函数
            }
            std::string context;
            Util::ReadFile("../wwwroot/404.html", &context);
            rsp->_statu = 404;
            rsp->SetContent(context, "text/html");
        }

        // 判断是否为静态资源请求
        bool IsFileHandler(const HttpRequest &req)
        {
            // 1. 必须设置了静态资源根目录
            if (_basedir.empty())
                return false;

            // 2. 请求方法，必须是GET / HEAD请求方法
            if (req._method != "GET" && req._method != "HEAD")
                return false;

            // 3. 请求的资源路径必须是一个合法路径
            if (Util::ValidPath(req._path) == false)
                return false;

            // 4. 请求的资源必须存在,且是一个普通文件
            // 有一种请求比较特殊 -- 目录：/, /image/， 这种情况给后边默认追加一个 index.html
            // index.html    /image/a.png
            // 不要忘了前缀的相对根目录,也就是将请求路径转换为实际存在的路径
            //   /image/a.png  ->   ./wwwroot/image/a.png
            std::string req_path = _basedir + req._path;
            if (req_path.back() == '/')
            {
                req_path += "index.html";
            }

            if (Util::IsRegular(req_path) == false)
                return false;
            return true;
        }

        // 静态资源请求的处理
        void FileHandler(const HttpRequest &req, HttpResponse *rsp)
        {
            std::string req_path = _basedir + req._path;
            if (req_path.back() == '/')
                req_path += "index.html";

            bool ret = Util::ReadFile(req_path, &rsp->_body);
            if (ret == false)
                return;

            std::string mime = Util::ExtMime(req_path);
            rsp->SetHeader("Content-Type", mime);
            return;
        }

        void Route(HttpRequest &req, HttpResponse *rsp, const std::string &ip)
        {
            // 1. 对请求进行分辨，是一个静态资源请求，还是一个功能性请求
            //    静态资源请求，则进行静态资源的处理
            //    功能性请求，则需要通过几个请求路由表来确定是否有处理函数
            //    既不是静态资源请求，也没有设置对应的功能性请求处理函数，就返回405
            if (IsFileHandler(req) == true)
            {
                // 是一个静态资源请求, 则进行静态资源请求的处理
                // LogMessage(DEBUG, "是一个静态资源请求");
                return FileHandler(req, rsp);
            }

            if (req._method == "GET" || req._method == "HEAD")
                return Dispatcher(req, rsp, _get_route, ip);
            else if (req._method == "POST")
                return Dispatcher(req, rsp, _post_route, ip);
            else if (req._method == "PUT")
                return Dispatcher(req, rsp, _put_route, ip);
            else if (req._method == "DELETE")
                return Dispatcher(req, rsp, _delete_route, ip);

            std::string context;
            Util::ReadFile("../wwwroot/405.html", &context);
            rsp->_statu = 405; // Method Not Allowed
            rsp->SetContent(context, "text/html");
            return;
        }

        // 设置上下文
        void OnConnected(const PtrConnection &conn)
        {
            conn->SetContext(HttpContext());
            LOG_INFO("OnConnected: new connection: {}", (void *)conn.get());
        }

        // 缓冲区数据解析+处理
        void OnMessage(const PtrConnection &conn, Buffer *buf)
        {
            while (buf->ReadAbleSize() > 0)
            {
                // 1. 获取上下文
                HttpContext *context = conn->GetContext()->get<HttpContext>();
                // 2. 通过上下文对缓冲区数据进行解析，得到HttpRequest对象
                //   1. 如果缓冲区的数据解析出错，就直接回复出错响应
                //   2. 如果解析正常，且请求已经获取完毕，才开始去进行处理
                context->RecvHttpRequest(buf);
                HttpRequest &req = context->Request();
                HttpResponse rsp(context->RespStatu());

                if (context->RespStatu() >= 400)
                {
                    // 进行错误响应，关闭连接
                    // 填充一个错误显示页面数据到rsp中
                    ErrorHandler(req, &rsp);
                    // 组织响应发送给客户端
                    WriteResponse(conn, req, rsp);
                    // 重置状态,不会一直处于RECV_HTTP_ERROR,不会不读取数据
                    context->ReSet();
                    // 出错了就把缓冲区数据清空
                    buf->MoveReadOffset(buf->ReadAbleSize());
                    // 关闭连接
                    conn->Shutdown();
                    return;
                }

                // 当前请求还没有接收完整,则退出，等新数据到来再重新继续处理
                if (context->RecvStatu() != RECV_HTTP_OVER)
                    return;

                // 3. 请求路由 + 业务处理
                std::string ip = conn->ClientIp();
                Route(req, &rsp, ip);
                // 4. 对HttpResponse进行组织发送
                WriteResponse(conn, req, rsp);
                // 5. 重置上下文
                context->ReSet();
                // 6. 根据长短连接判断是否关闭连接或者继续处理--
                // 短链接则直接关闭
                if (rsp.Close() == true)
                {
                    conn->Shutdown();
                }
            }
        }

    public:
        HttpServer(int port, int timeout = Config::GetInstance().timeout) : _server(port)
        {
            _server.EnableInactiveRelease(timeout);
            _server.SetConnectedCallback(std::bind(&HttpServer::OnConnected, this, std::placeholders::_1));
            _server.SetMessageCallback(std::bind(&HttpServer::OnMessage, this,
                                                 std::placeholders::_1, std::placeholders::_2));
        }

        // 设置资源根目录
        void SetBaseDir(const std::string &path) { _basedir = path; }

        // 设置各种请求方法的回调函数
        void Get(const std::string &pattern, const Handler &handler)
        {
            _get_route.push_back(std::make_pair(std::regex(pattern), handler));
        }
        void Post(const std::string &pattern, const Handler &handler)
        {
            _post_route.push_back(std::make_pair(std::regex(pattern), handler));
        }
        void Put(const std::string &pattern, const Handler &handler)
        {
            _put_route.push_back(std::make_pair(std::regex(pattern), handler));
        }
        void Delete(const std::string &pattern, const Handler &handler)
        {
            _delete_route.push_back(std::make_pair(std::regex(pattern), handler));
        }

        // 设置线程数量
        void SetThreadCount(int count) { _server.SetThreadCount(count); }

        // 启动服务器
        void Start() { _server.Start(); }
    };
}